﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TasksModel
{
    //todo add regex string search option
    [System.Xml.Serialization.XmlRootAttribute(Namespace = "http://mp.tasks.com/2010/", IsNullable = false)]
    public class TaskSearchCriteria : IdentifiedObject
    {
        #region Properties

        private string _author;

        public string Author
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _author; }
            [System.Diagnostics.DebuggerStepThrough]
            set { _author = value; }
        }

        private string _name;

        public string Name
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _name; }
            [System.Diagnostics.DebuggerStepThrough]
            set { _name = value; }
        }

        private string _content;

        public string Content
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _content; }
            [System.Diagnostics.DebuggerStepThrough]
            set { _content = value; }
        }

        private string _status;

        public string Status
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _status; }
            [System.Diagnostics.DebuggerStepThrough]
            set { _status = value; }
        }

        private string _flag;

        public string Flag
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _flag; }
            [System.Diagnostics.DebuggerStepThrough]
            set { _flag = value; }
        }

        #region Changed

        private DateTime? _changedAtFrom;

        /// <summary>
        /// In UTC.
        /// </summary>
        public DateTime? ChangedAtFrom
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _changedAtFrom; }
            [System.Diagnostics.DebuggerStepThrough]
            set { _changedAtFrom = value; }
        }

        private DateTime? _changedAtTo;

        /// <summary>
        /// In UTC.
        /// </summary>
        public DateTime? ChangedAtTo
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _changedAtTo; }
            [System.Diagnostics.DebuggerStepThrough]
            set { _changedAtTo = value; }
        }

        #endregion

        #region Created

        private DateTime? _createdAtFrom;

        /// <summary>
        /// In UTC.
        /// </summary>
        public DateTime? CreatedAtFrom
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _createdAtFrom; }
            [System.Diagnostics.DebuggerStepThrough]
            set { _createdAtFrom = value; }
        }

        private DateTime? _createdAtTo;

        /// <summary>
        /// In UTC.
        /// </summary>
        public DateTime? CreatedAtTo
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _createdAtTo; }
            [System.Diagnostics.DebuggerStepThrough]
            set { _createdAtTo = value; }
        }

        #endregion

        #region Due

        private DateTime? _dueAtFrom;

        /// <summary>
        /// In UTC.
        /// </summary>
        public DateTime? DueAtFrom
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _dueAtFrom; }
            [System.Diagnostics.DebuggerStepThrough]
            set { _dueAtFrom = value; }
        }

        private DateTime? _dueAtTo;

        /// <summary>
        /// In UTC.
        /// </summary>
        public DateTime? DueAtTo
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _dueAtTo; }
            [System.Diagnostics.DebuggerStepThrough]
            set { _dueAtTo = value; }
        }

        #endregion

        private HashSet<TaskClass> _classes;

        public HashSet<TaskClass> Classes
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _classes; }
            [System.Diagnostics.DebuggerStepThrough]
            set { _classes = value; }
        }

        private HashSet<PriorityClass> _priorities;

        public HashSet<PriorityClass> Priorities
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _priorities; }
            [System.Diagnostics.DebuggerStepThrough]
            set { _priorities = value; }
        }

        private HashSet<string> _tags;

        public HashSet<string> Tags
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _tags; }
            [System.Diagnostics.DebuggerStepThrough]
            set { _tags = value; }
        }

        #endregion

        public TaskSearchCriteria(Guid id)
        {
            ID = id;
            _classes = new HashSet<TaskClass>();
            _priorities = new HashSet<PriorityClass>();
            _tags = new HashSet<string>();
        }

        public TaskSearchCriteria()
            : this(Guid.NewGuid())
        {
        }

        /// <summary>
        /// Find the first task meeting criteria.
        /// </summary>
        /// <param name="tl">library to search</param>
        /// <param name="includeArchived">include archived</param>
        /// <returns>task or null</returns>
        public Task Find(TaskLibrary tl, bool includeArchived)
        {
            System.Diagnostics.Debug.Assert(tl != null);
            foreach (Task t in tl.Tasks.ImportantUrgentTasks)
                if (IsMatch(t))
                    return t;
            foreach (Task t in tl.Tasks.ImportantNotUrgentTasks)
                if (IsMatch(t))
                    return t;
            foreach (Task t in tl.Tasks.UnimportantUrgentTasks)
                if (IsMatch(t))
                    return t;
            foreach (Task t in tl.Tasks.UnimportantNotUrgentTasks)
                if (IsMatch(t))
                    return t;
            if (includeArchived)
                foreach (Task t in tl.Tasks.Archived)
                    if (IsMatch(t))
                        return t;
            return null;
        }

        /// <summary>
        /// Return all tasks meeting criteria.
        /// </summary>
        /// <param name="tl">library to search</param>
        /// <param name="includeArchived">include archive</param>
        /// <returns>not null, no order of items imposed</returns>
        public TaskObservableCollection<Task> Select(TaskLibrary tl, bool includeArchived)
        {
            System.Diagnostics.Debug.Assert(tl != null);
            TaskObservableCollection<Task> res = new TaskObservableCollection<Task>();
            res.SuspendChangedEvents();
            foreach (Task t in tl.Tasks.ImportantUrgentTasks)
                if (IsMatch(t))
                    res.Add(t);
            foreach (Task t in tl.Tasks.ImportantNotUrgentTasks)
                if (IsMatch(t))
                    res.Add(t);
            foreach (Task t in tl.Tasks.UnimportantUrgentTasks)
                if (IsMatch(t))
                    res.Add(t);
            foreach (Task t in tl.Tasks.UnimportantNotUrgentTasks)
                if (IsMatch(t))
                    res.Add(t);
            if (includeArchived)
                foreach (Task t in tl.Tasks.Archived)
                    if (IsMatch(t))
                        res.Add(t);
            res.ResumeChangedEvents();
            return res;
        }

        public bool IsMatch(Task t)
        {
            System.Diagnostics.Debug.Assert(t != null);
            bool res = false;
            if (!res && Author != null && t.CreatedBy != null)
                res = IsMatch(Author, t.CreatedBy.Name);

            if (!res && Name != null)
                res = IsMatch(Name, t.Name);

            if (!res && Content != null)
                res = IsMatch(Content, t.RichContent);

            if (!res && Status != null && t.Status != null)
                res = IsMatch(Status, t.Status.Name);

            if (!res && Flag != null && t.Flags != null)
                res = IsMatch(Flag, t.Flags);

            if (!res && Classes != null)
                res = IsMatch(Classes, delegate(TaskClass tc)
                {
                    return tc == t.Class;
                });

            if (!res && Priorities != null)
                res = IsMatch(Priorities, delegate(PriorityClass pc)
                {
                    return pc == t.Priority;
                });

            if (!res && Tags != null && t.Tags != null)
                res = Tags.Any(delegate(string tag)
                {
                    return t.Tags.Any(delegate(string tn)
                    {
                        return IsMatch(tn, tag);
                    });
                });

            if (!res && t.DueDate.HasValue)
                res = IsMatch(t.DueDate.Value, DueAtFrom, DueAtTo);

            if (!res && t.ChangedAt.HasValue)
                res = IsMatch(t.ChangedAt.Value, ChangedAtFrom, ChangedAtTo);

            if (!res)
                res = IsMatch(t.CreatedAt, CreatedAtFrom, CreatedAtTo);

            return res;
        }

        #region Helpers

        private static bool IsMatch(DateTime needle, DateTime? from, DateTime? to)
        {
            if (needle == DateTime.MinValue)
                return false;
            if (to.HasValue)
            {
                if (from.HasValue)
                    return from.Value <= needle && needle <= to.Value;
                return needle <= to.Value;
            }
            else
                return from.HasValue && from.Value <= needle;
        }

        private static bool IsMatch<T>(HashSet<T> needles, Func<T, bool> predicate)
        {
            System.Diagnostics.Debug.Assert(needles != null);
            return needles.Any(predicate);
        }

        private static bool IsMatch<T>(string needle, TaskObservableCollection<T> haystack)
            where T : NamedObject
        {
            System.Diagnostics.Debug.Assert(needle != null);
            System.Diagnostics.Debug.Assert(haystack != null);
            return haystack.Any(delegate(T t) { return IsMatch(needle, t.Name); });
        }

        private static bool IsMatch(string needle, string haystack)
        {
            System.Diagnostics.Debug.Assert(needle != null);
            return haystack != null && haystack.IndexOf(needle, StringComparison.CurrentCultureIgnoreCase) > 0;
        }

        #endregion
    }
}